//------------------------------------------------------------------------------
// Original code by Christian Graus, modified by Ste Brennan for DNAide. 
// http://www.codeproject.com/cs/media/csharpgraphicfilters11.asp
//------------------------------------------------------------------------------
using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace DNAide.Drawing
{
	public static partial class BitmapAide
	{

		/// <summary>
		/// Inverts a bitmap
		/// </summary>
		/// <param name="bitmap"></param>
		public static void Invert(Bitmap bitmap)
		{
			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte* p = (byte*)(void*)Scan0;

				int nOffset = stride - bitmap.Width * 3;
				int nWidth = bitmap.Width * 3;

				for (int y = 0; y < bitmap.Height; ++y)
				{
					for (int x = 0; x < nWidth; ++x)
					{
						p[0] = (byte)(255 - p[0]);
						++p;
					}
					p += nOffset;
				}
			}

			bitmap.UnlockBits(bmData);
		}


		/// <summary>
		/// GrayScale
		/// </summary>
		/// <param name="bitmap"></param>
		public static void GrayScale(Bitmap bitmap)
		{
			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte* p = (byte*)(void*)Scan0;

				int nOffset = stride - bitmap.Width * 3;

				byte red, green, blue;

				for (int y = 0; y < bitmap.Height; ++y)
				{
					for (int x = 0; x < bitmap.Width; ++x)
					{
						blue = p[0];
						green = p[1];
						red = p[2];

						p[0] = p[1] = p[2] = (byte)(.299 * red + .587 * green + .114 * blue);

						p += 3;
					}
					p += nOffset;
				}
			}

			bitmap.UnlockBits(bmData);
		}


		/// <summary>
		/// Adjust brightness
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="brightness">-255 to 255</param>
		public static void Brightness(Bitmap bitmap, Int32 brightness)
		{
			if (brightness < -255 || brightness > 255)
				throw new ArgumentException(Resources.BitmapAide.BrightnessValueError, "brightness");

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			int nVal = 0;

			unsafe
			{
				byte* p = (byte*)(void*)Scan0;

				int nOffset = stride - bitmap.Width * 3;
				int nWidth = bitmap.Width * 3;

				for (int y = 0; y < bitmap.Height; ++y)
				{
					for (int x = 0; x < nWidth; ++x)
					{
						nVal = (int)(p[0] + brightness);

						if (nVal < 0) nVal = 0;
						if (nVal > 255) nVal = 255;

						p[0] = (byte)nVal;

						++p;
					}
					p += nOffset;
				}
			}

			bitmap.UnlockBits(bmData);
		}


		/// <summary>
		/// Adjust contrast
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="contrast">-100 to 100</param>
		public static void Contrast(Bitmap bitmap, Int16 contrast)
		{
			if (contrast < -100 || contrast > 100)
				throw new ArgumentException(Resources.BitmapAide.ContrastValueError, "contrast");

			double pixel = 0, calculatedContrast = (100.0 + contrast) / 100.0;

			calculatedContrast *= calculatedContrast;

			int red, green, blue;

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte* p = (byte*)(void*)Scan0;

				int nOffset = stride - bitmap.Width * 3;

				for (int y = 0; y < bitmap.Height; ++y)
				{
					for (int x = 0; x < bitmap.Width; ++x)
					{
						blue = p[0];
						green = p[1];
						red = p[2];

						pixel = red / 255.0;
						pixel -= 0.5;
						pixel *= calculatedContrast;
						pixel += 0.5;
						pixel *= 255;
						if (pixel < 0) pixel = 0;
						if (pixel > 255) pixel = 255;
						p[2] = (byte)pixel;

						pixel = green / 255.0;
						pixel -= 0.5;
						pixel *= calculatedContrast;
						pixel += 0.5;
						pixel *= 255;
						if (pixel < 0) pixel = 0;
						if (pixel > 255) pixel = 255;
						p[1] = (byte)pixel;

						pixel = blue / 255.0;
						pixel -= 0.5;
						pixel *= calculatedContrast;
						pixel += 0.5;
						pixel *= 255;
						if (pixel < 0) pixel = 0;
						if (pixel > 255) pixel = 255;
						p[0] = (byte)pixel;

						p += 3;
					}
					p += nOffset;
				}
			}

			bitmap.UnlockBits(bmData);
		}


		/// <summary>
		/// Adjust gamma
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="red">.2 to 5</param>
		/// <param name="green">.2 to 5</param>
		/// <param name="blue">.2 to 5</param>
		public static void Gamma(Bitmap bitmap, Double red, Double green, Double blue)
		{
			if (red < .2 || red > 5)
				throw new ArgumentException(Resources.BitmapAide.GammaColorValueError, "red");

			if (green < .2 || green > 5)
				throw new ArgumentException(Resources.BitmapAide.GammaColorValueError, "green");

			if (blue < .2 || blue > 5)
				throw new ArgumentException(Resources.BitmapAide.GammaColorValueError, "blue");

			byte[] redGamma = new byte[256];
			byte[] greenGamma = new byte[256];
			byte[] blueGamma = new byte[256];

			for (int i = 0; i < 256; ++i)
			{
				redGamma[i] = (byte)Math.Min(255, (int)((255.0 * Math.Pow(i / 255.0, 1.0 / red)) + 0.5));
				greenGamma[i] = (byte)Math.Min(255, (int)((255.0 * Math.Pow(i / 255.0, 1.0 / green)) + 0.5));
				blueGamma[i] = (byte)Math.Min(255, (int)((255.0 * Math.Pow(i / 255.0, 1.0 / blue)) + 0.5));
			}

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte* p = (byte*)(void*)Scan0;

				int nOffset = stride - bitmap.Width * 3;

				for (int y = 0; y < bitmap.Height; ++y)
				{
					for (int x = 0; x < bitmap.Width; ++x)
					{
						p[2] = redGamma[p[2]];
						p[1] = greenGamma[p[1]];
						p[0] = blueGamma[p[0]];

						p += 3;
					}
					p += nOffset;
				}
			}

			bitmap.UnlockBits(bmData);
		}

		/// <summary>
		/// Adds or subracts a value to each color
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="red">-255 to 255</param>
		/// <param name="green">-255 to 255</param>
		/// <param name="blue">-255 to 255</param>
		/// <returns></returns>
		public static void ColorFilter(Bitmap bitmap, Int32 red, Int32 green, Int32 blue)
		{
			if (red < -255 || red > 255)
				throw new ArgumentException(Resources.BitmapAide.ColorFilterColorValueError, "red");

			if (green < -255 || green > 255)
				throw new ArgumentException(Resources.BitmapAide.ColorFilterColorValueError, "green");

			if (blue < -255 || blue > 255)
				throw new ArgumentException(Resources.BitmapAide.ColorFilterColorValueError, "blue");

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte* p = (byte*)(void*)Scan0;

				int nOffset = stride - bitmap.Width * 3;
				int nPixel;

				for (int y = 0; y < bitmap.Height; ++y)
				{
					for (int x = 0; x < bitmap.Width; ++x)
					{
						nPixel = p[2] + red;
						nPixel = Math.Max(nPixel, 0);
						p[2] = (byte)Math.Min(255, nPixel);

						nPixel = p[1] + green;
						nPixel = Math.Max(nPixel, 0);
						p[1] = (byte)Math.Min(255, nPixel);

						nPixel = p[0] + blue;
						nPixel = Math.Max(nPixel, 0);
						p[0] = (byte)Math.Min(255, nPixel);

						p += 3;
					}
					p += nOffset;
				}
			}

			bitmap.UnlockBits(bmData);
		}

	}
}
